/**
 * Copyright (c) 2008, Mu Dynamics.
 *  All rights reserved.
 *  
 *   Redistribution and use in source and binary forms, with or without modification, 
 *   are permitted provided that the following conditions are met:
 *   
 *  - Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation and/or 
 *     other materials provided with the distribution.
 *  - Neither the name of the "Mu Dynamics" nor the names of its contributors may be used 
 *     to endorse  or promote products derived from this software without specific prior 
 *     written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.mu.jacob.core.model.decorator;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.SimpleElementVisitor6;

import com.mu.jacob.core.generator.Decorate;

/**
 * TypeElement decorator
 * @author Adam Smyczek
 *
 */
public class DefaultTypeElement
extends DefaultElement<TypeElement>
implements TypeElement {

	/** lazy initialized set of imports for a interface/class */
	private Set<Element> imports = null; 

	/**
	 * @see com.mu.jacob.core.model.decorator.DefaultElement#getApiClass()
	 */
	@Override
	public final Class<TypeElement> getApiClass() {
		return TypeElement.class;
	}

	/**
	 * @see javax.lang.model.element.TypeElement#getInterfaces()
	 */
	@Decorate
	public List<? extends TypeMirror> getInterfaces() {
		return getDecorated().getInterfaces();
	}

	/**
	 * @see javax.lang.model.element.TypeElement#getNestingKind()
	 */
	public NestingKind getNestingKind() {
		return getDecorated().getNestingKind();
	}

	/**
	 * @see javax.lang.model.element.TypeElement#getQualifiedName()
	 */
	public Name getQualifiedName() {
		return getDecorated().getQualifiedName();
	}

	/**
	 * @see javax.lang.model.element.TypeElement#getSuperclass()
	 */
	@Decorate
	public TypeMirror getSuperclass() {
		return getDecorated().getSuperclass();
	}

	/**
	 * Check if this element implements the qualifiedName class 
	 * @param qualifiedName of the super class
	 * @return true, if this element implements a super class with qualifiedName
	 * TODO: add check for implements
	 */
	public boolean isA(String qualifiedName) {
		if (qualifiedName != null) {
			TypeElement element = this;
			while ((element != null) &&
					(!qualifiedName.equals(element.getQualifiedName().toString()))) {
				// get superclass
				if ((element.getSuperclass() != null) && (element.getSuperclass() instanceof DeclaredType) &&
					(((DeclaredType)element.getSuperclass()).asElement() instanceof TypeElement)) {
					element = (TypeElement)((DeclaredType)element.getSuperclass()).asElement();
				} else {
					element = null;
				}
			}
			return (element != null);
		}
		return false;
	}

	/**
	 * @see javax.lang.model.element.TypeElement#getTypeParameters()
	 */
	@Decorate
	public List<? extends TypeParameterElement> getTypeParameters() {
		return getDecorated().getTypeParameters();
	}
	
	/**
	 * Collects and returns imports of the type element
	 * @return returns a set of imports
	 */
	@Decorate
	public Set<Element> getImports() {
		if (imports == null) {
			imports = new HashSet<Element>();
			new ImportsCollectorVisitor(imports).visit(getDecorated());
			
			// remove default objects
			// TODO: this can be done much nicer!
			Iterator<Element> iter = imports.iterator();
			while (iter.hasNext()) {
				Element element = iter.next();
				
				if (element instanceof TypeElement) {
					if (Object.class.getName().equals(((TypeElement)element).getQualifiedName().toString())) {
						iter.remove();
					}
				}
			}
		}
		return imports;
	}
	
	/**
	 * A element visitor for import collection 
	 */
	private static class ImportsCollectorVisitor
	extends SimpleElementVisitor6<ImportsCollectorVisitor, Void> {
		/** Set of collected imports */
		private final Set<Element> imports;
		
		private void addElement(Element element) {
			if (element != null) {
				imports.add(element);
			}
		}

		private void addElement(TypeMirror  type) {
			if (type instanceof TypeVariable) {
				addElement(((TypeVariable)type).asElement());
			} else if (type instanceof DeclaredType) {
				addElement(((DeclaredType)type).asElement());
			}
		}

		public ImportsCollectorVisitor(final Set<Element> imports) {
			super();
			this.imports = imports;
		}

		@Override
		protected ImportsCollectorVisitor defaultAction(Element e, Void p) {
			return this;
		}

		@Override
		public ImportsCollectorVisitor visitExecutable(ExecutableElement e, Void p) {
			for (VariableElement variableElement : e.getParameters()) {
				addElement(variableElement.asType());
			}
			
			if (e.getReturnType() != null) {
				addElement(e.getReturnType());
			}
			
			return this;
		}

		@Override
		public ImportsCollectorVisitor visitType(TypeElement e, Void p) {
			addElement(e.getSuperclass());
			
			for (TypeMirror interf : e.getInterfaces()) {
				addElement(interf);
			}
			
			for (TypeParameterElement typeParameterElement : e.getTypeParameters()) {
				imports.add(typeParameterElement);
			}
			
			return this;
		}

		@Override
		public ImportsCollectorVisitor visitTypeParameter(TypeParameterElement e, Void p) {
			imports.add(e);
			return this;
		}

		@Override
		public ImportsCollectorVisitor visitVariable(VariableElement e, Void p) {
			addElement(e.asType());
			return this;
		}

	}	

}
